/**
 * @file proc.c
 * @date 23.04.26
 * @author xin
 * @brief 进程相关定义
 * @version 0.1
 * @status Unstable
*/

#include "types.h"
#include "defs.h"
#include "riscv.h"
#include "param.h"
#include "proc.h" 
#include "memlayout.h"

// 可周转的cpu
struct cpu cpus[NCPU];

// 可周转的进程
struct proc proc[NPROC];

// 零号进程
struct proc *initproc;

int nextpid = 1;

/**
 * @brief 释放进程p
 * @param p 待释放的进程
*/
static void freeproc(struct proc *p);

/**
 * @brief fork申请的新进程初始化后第一步进入的函数，以返回用户空间
*/
extern void forkret(void);

extern char trampoline[]; // trampoline.S

void
proc_mapstacks(pagetable_t kpgtbl)
{
    struct proc *p;
    char *pa;
    for (p = proc; p < &proc[NPROC]; p++) {
        if ((pa = kalloc()) == 0) panic("kalloc");
        uint64 va = KSTACK((int)(p - proc));
        kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
    }
}

void
procinit(void)
{
    struct proc *p;

    for (p = proc; p < & proc[NPROC]; p++) {
        p->state = UNUSED;
        p->kstack = KSTACK((int)(p - proc));
    }
}

int
cpuid()
{
    int id = r_tp();
    return id;
}

struct cpu*
mycpu(void)
{
    int id = cpuid();
    struct cpu *c = &cpus[id];
    return c;
}

struct proc*
myproc(void)
{
    struct cpu *c = mycpu();
    struct proc *p = c->proc;
    return p;
}

/**
 * @brief 申请下一个进程的id
 * @return 申请到的进程id
*/
int 
allocpid()
{
    int pid;
    pid = nextpid;
    nextpid++;
    return pid;
}

/**
 * @brief 在所有进程列表中找到一个UNUSED的进程，为其初始化状态
 * @brief 包括申请trapframe页、页表、初始化上下文
 * @return 申请到的新进程；若申请失败，返回0
*/
static struct proc*
allocproc(void)
{
    struct proc *p;

    for (p = proc; p < &proc[NPROC]; p++) {
        if (p->state == UNUSED) 
            goto found;
    }
    return 0;

found:
    p->pid = allocpid();
    p->state = USED;

    // 申请trapframe页
    if ((p->trapframe = (struct trapframe *)kalloc()) == 0) {
        freeproc(p);
        return 0;
    }

    // 申请用户页表
    if ((p->pagetable = proc_pagetable(p)) == 0) {
        freeproc(p);
        return 0;
    }

    // 初始化该进程上下文
    // 切换上下文后的返回地址ra设置为forkret，sp设置为该进程在内核中的栈
    memset(&p->context, 0, sizeof(p->context));
    p->context.ra = (uint64) forkret;
    p->context.sp = p->kstack + PGSIZE;

    return p;
}

static void
freeproc(struct proc *p) 
{
    // 1. 释放trapframe
    if (p->trapframe) kfree((void*) p->trapframe);
    p->trapframe = 0;
    // 2. 释放页表
    if (p->pagetable) proc_freepagetable(p->pagetable, p->sz);
    p->pagetable = 0;
    // 3. 重置其他内容
    p->sz = 0;
    p->pid = 0;
    p->parent = 0;
    p->name[0] = 0;
    p->chan = 0;
    p->killed = 0;
    p->xstate = 0;
    p->state = UNUSED;
}

pagetable_t
proc_pagetable(struct proc *p)
{
    pagetable_t pagetable;

    // 申请一个全是0空页表
    if ((pagetable = uvmcreate()) == 0) return 0;

    // 映射到trampoline页，其中映射的虚拟地址都是TRAMPOLIME的值
    // 同时只有supervisor在往返用户空间过程中使用trampoline的代码，不用设置PTE_U
    if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0) {
        uvmfree(pagetable, 0);
    }

    // 映射trapframe页，该页仅在trampoline页下面
    if (mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe), PTE_R | PTE_W) < 0) {
        // 先取消映射之前的trampoline
        uvmunmap(pagetable, TRAMPOLINE, 1, 0);
        // 再释放页表
        uvmfree(pagetable, 0);
        return 0;
    }

    return pagetable;
}

void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
    // 先取消映射TRAMPOLINE和TRAPFRAME，再释放页表
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
    uvmfree(pagetable, sz);
}

// 第一个用户程序:initcode
uchar initcode[] = {
  0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x45, 0x02,
  0x97, 0x05, 0x00, 0x00, 0x93, 0x85, 0x35, 0x02,
  0x93, 0x08, 0x70, 0x00, 0x73, 0x00, 0x00, 0x00,
  0x93, 0x08, 0x20, 0x00, 0x73, 0x00, 0x00, 0x00,
  0xef, 0xf0, 0x9f, 0xff, 0x2f, 0x69, 0x6e, 0x69,
  0x74, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

void
userinit(void)
{
    struct proc *p;
    
    // 按照fork子进程的流程创建零号进程
    p = allocproc();
    initproc = p;

    // 申请一个用户页存放initcode程序
    uvmfirst(p->pagetable, initcode, sizeof(initcode));
    p->sz = PGSIZE;

    // 针对零号进程准备独特的返回地址epc，对应initcode代码
    p->trapframe->epc = 0;
    p->trapframe->sp = PGSIZE;

    safestrcpy(p->name, "initcode", sizeof(p->name));
    p->cwd = namei("/");

    p->state = RUNNABLE;
}

void
forkret(void)
{
    static int first = 1;

    if (first) {    // 针对零号进程
        first = 0;
        fsinit(ROOTDEV);
    }

    usertrapret();
}

int
growproc(int n)
{
    uint64 sz;
    struct proc *p = myproc();

    sz = p->sz;
    if (n > 0) {
        if ((sz = uvmalloc(p->pagetable, sz, sz+n, PTE_W)) == 0) 
            return -1;
    } else if (n < 0) 
        sz = uvmdealloc(p->pagetable, sz, sz+n);
    p->sz = sz;
    return 0;
}

/**
 * @brief 将进程p的废弃子进程全都移交给零号进程
 * @param p 待移交子进程的进程
*/
void
reparent(struct proc *p)
{
    struct proc *pp;

    for (pp = proc; pp < &proc[NPROC]; pp++) {
        if (pp->parent == p) {
            pp->parent = initproc;
            wakeup(initproc);           
        }
    }
}

void
scheduler(void)
{
    struct proc *p;
    struct cpu *c = mycpu();

    // 初始化空闲进程
    c->proc = 0;
    while (1)
    {
        // 使能中断，确保设备可以中断以防止死锁
        intr_on();

        for (p = proc; p < &proc[NPROC]; p++) {
            if (p->state == RUNNABLE) {
                // 可运行的进程与cpu携带的进程进行切换上下文
                p->state = RUNNING;
                c->proc = p;
                // 切换上下文运行进程中的程序
                swtch(&c->context, &p->context);

                // 此时进程中的程序已经运行完毕，返回到此处
                // 并且进程应该在返回之前改变了状态p->state
                c->proc = 0;
            }
        }
    }    
}

void
exit(int status)
{
    struct proc *p = myproc();

    if (p == initproc) panic("init exiting");

    // 1. 关文件
    for (int fd = 0; fd < NOFILE; fd++) {
        if (p->ofile[fd]) {
            struct file *f = p->ofile[fd];
            fileclose(f);
            p->ofile[fd] = 0;
        }
    }

    // 2. 删除进程p对指向目录的引用
    iput(p->cwd);
    p->cwd = 0;

    // 3. 将p的全部子进程移交到零号进程
    reparent(p);

    // 4. 唤醒p的父进程（父进程可能在wait()中sleep）
    wakeup(p->parent);

    // 修改p状态
    p->xstate = status;
    p->state = ZOMBIE;     // 在父进程回收p之前，p作为僵尸进程存在

    sched();
    // 不应该运行到此处
    panic("zombie exit");
}

int
wait(uint64 addr)
{
    struct proc *pp;
    int havekids, pid;
    struct proc *p = myproc();

    while(1) {
        // 搜索进程表中p的已经exit的子进程
        havekids = 0;
        for (pp = proc; pp < &proc[NPROC]; pp++) {
            if (pp->parent == p) {
                havekids = 1;
                if (pp->state == ZOMBIE) {  // exit()中已经设置该子进程状态为ZOMBIE，若不是说明还没有exit
                    pid = pp->pid;
                    if (addr != 0 && copyout(p->pagetable, addr, (char*)&pp->xstate, sizeof(pp->xstate)) < 0) {
                        return -1;  // 存放子进程状态失败，若存放地址为空，说明不需要存放
                    }
                    freeproc(pp);
                    return pid;
                }
            }
        }

        if (!havekids || killed(p)) {
            return -1;  // 没有子进程 或 该进程都已经被killed
        }

        sleep(p);
    }
}


void
sched(void)
{
    // int intena;
    struct proc *p = myproc();

    intr_off();

    // 此处暂不需要持有锁
    // if(!holding(&p->lock))
    //    panic("sched p->lock");
    // 也没用到pushoff，暂不涉及noff
    // if(mycpu()->noff != 1)
    //    panic("sched locks");   
    if (intr_get())
        panic("sched interruptible");
    
    swtch(&p->context, &mycpu()->context);
}

void
sleep(void* chan)
{
    struct proc *p = myproc();

    p->chan = chan;
    p->state = SLEEPING;

    sched();

    p->chan = 0;
}

void
wakeup(void* chan)
{
    struct proc *p;

    for (p = proc; p < &proc[NPROC]; p++) {
        if (p != myproc()) {
            if (p->state == SLEEPING && p->chan == chan)
                p->state = RUNNABLE;
        }
    }
}

void
yield(void)
{
    struct proc *p = myproc();
    p->state = RUNNABLE;
    sched();
}

int
kill(int pid)
{
    struct proc *p;

    for (p = proc; p < &proc[NPROC]; p++) {
        if (p->pid == pid) {
            p->killed = 1;
            if (p->state == SLEEPING) { // 将SLEEP的进程唤醒，RUNNABLE的进程才会返回到用户空间
                p->state = RUNNABLE;
            }
            return 0;
        }
    }
    return -1;
}

void
setkilled (struct proc *p) 
{
    p->killed = 1;
}

int 
killed(struct proc *p)
{
    int k;
    k = p->killed;
    return k;
}

int
either_copyout(int usr_dst, uint64 dst, void *src, uint64 len)
{
    struct proc *p = myproc();
    if (usr_dst) {
        return copyout(p->pagetable, dst, src, len);
    } else {
        memmove((char *)dst, src, len);
        return 0;
    }
}

int
either_copyin(void *dst, int usr_src, uint64 src, uint64 len)
{
    struct proc *p = myproc();
    if (usr_src) {
        return copyin(p->pagetable, dst, src, len);
    } else {
        memmove(dst, (char*)src, len);
        return 0;
    }
}

void
procdump(void)
{
    static char *states[] = {
        [UNUSED]    "unused",
        [USED]      "used",
        [SLEEPING]  "sleep",
        [RUNNABLE]  "runnable",
        [RUNNING]   "running",
        [ZOMBIE]    "zombie"
    };
    struct proc *p;
    char *state;

    printf("\n");
    for (p = proc; p < &proc[NPROC]; p++) {
        if (p->state == UNUSED)
            continue;
        if (p->state >= 0 && p->state < NELEM(states) && states[p->state])
            state = states[p->state];
        else
            state = "???Unknown state???";
        printf("%d %s %s", p->pid, state, p->name);
        printf("\n");
    }
}
